package com.yYclass.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.yYclass.component.ShortLinkComponent;
import com.yYclass.config.RabbitMQAddConfig;
import com.yYclass.config.RabbitMQDelConfig;
import com.yYclass.config.RabbitMQUpdateConfig;
import com.yYclass.controller.request.ShortLinkAddRequest;
import com.yYclass.controller.request.ShortLinkDelRequest;
import com.yYclass.controller.request.ShortLinkPageRequest;
import com.yYclass.controller.request.ShortLinkUpdateRequest;
import com.yYclass.enums.DomainTypeEnum;
import com.yYclass.enums.EventMessageType;
import com.yYclass.enums.ShortLinkStateEnum;
import com.yYclass.interceptor.LoginInterceptor;
import com.yYclass.model.*;
import com.yYclass.mapper.ShortLinkMapper;
import com.yYclass.service.DomainService;
import com.yYclass.service.GroupCodeMappingService;
import com.yYclass.service.LinkGroupService;
import com.yYclass.service.ShortLinkService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yYclass.util.CommonUtil;
import com.yYclass.util.IDUtil;
import com.yYclass.util.JsonData;
import com.yYclass.util.JsonUtil;
import com.yYclass.vo.ShortLinkVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 栗永松
 * @since 2024-09-14
 */
@Service
@Slf4j
public class ShortLinkServiceImpl extends ServiceImpl<ShortLinkMapper, ShortLinkDO> implements ShortLinkService {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private RabbitMQAddConfig rabbitMQConfig;
    @Autowired
    private RabbitMQDelConfig rabbitMQDelConfig;
    @Autowired
    private RabbitMQUpdateConfig rabbitMQUpdateConfig;
    @Autowired
    private ShortLinkMapper shortLinkMapper;
    @Autowired
    private ShortLinkService shortLinkService;
    @Autowired
    private DomainService domainService;

    @Autowired
    private LinkGroupService linkGroupService;

    @Autowired
    private ShortLinkComponent shortLinkComponent;

    @Autowired
    private GroupCodeMappingService groupCodeMappingService;
    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;
    @Override
    public int addShortLink(ShortLinkDO shortLinkDO) {
        return shortLinkMapper.insert(shortLinkDO);
    }

    @Override
    public ShortLinkDO findByShortLinCode(String shortLinkCode) {

        ShortLinkDO shortLinkDO = shortLinkMapper.selectOne(
                new QueryWrapper<ShortLinkDO>().eq("code", shortLinkCode));
        return shortLinkDO;
    }

    @Override
    public int del(ShortLinkDO shortLinkDOo) {
        int rows = shortLinkMapper.update(null,
                new UpdateWrapper<ShortLinkDO>().eq("code", shortLinkDOo.getCode())
                        .eq("account_no", shortLinkDOo.getAccountNo())
                        .set("del",1));
        return rows;
    }

    @Override
    public ShortLinkVO parseShortLinkCode(String shortLinkCode) {
        ShortLinkDO shortLinkDO = shortLinkMapper.selectOne(new QueryWrapper<ShortLinkDO>().eq("code", shortLinkCode));
        if(shortLinkDO == null){
            return null;
        }
        ShortLinkVO shortLinkVO = new ShortLinkVO();
        BeanUtils.copyProperties(shortLinkDO,shortLinkVO);
        return shortLinkVO;
    }

    @Override
    public JsonData createShortLink(ShortLinkAddRequest request) {
        Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();
        request.setOriginalUrl(CommonUtil.addUrlPrefix(request.getOriginalUrl()));
        EventMessage eventMessage = EventMessage.builder().accountNo(accountNo)
                .content(JsonUtil.obj2Json(request))
                .messageId(IDUtil.geneSnowFlakeID().toString())
                .eventMessageType(EventMessageType.SHORT_LINK_ADD.name())
                .build();
        rabbitTemplate.convertAndSend(rabbitMQConfig.getShortLinkEventExchange(), rabbitMQConfig.getShortLinkAddRoutingKey(),eventMessage);
        return JsonData.buildSuccess();
    }

    /**
     * 处理短链新增逻辑
     * <p>
     * //判断短链域名是否合法
     * //判断组名是否合法
     * //生成长链摘要
     * //生成短链码
     * //加锁
     * //查询短链码是否存在
     * //构建短链对象
     * //保存数据库
     *
     * @param eventMessage
     * @return
     */
    @Override
    public boolean handleAddShortLink(EventMessage eventMessage) {
        Long accountNo = eventMessage.getAccountNo();
        String messageType = eventMessage.getEventMessageType();
        ShortLinkAddRequest addRequest = JsonUtil.json2Obj(eventMessage.getContent(), ShortLinkAddRequest.class);
        //短链域名校验
        DomainDO domainDO = checkDomain(addRequest.getDomainType(),addRequest.getDomainId(),accountNo);
        //校验组是否合法
        LinkGroupDO linkGroupDO = checkLinkGroup(addRequest.getGroupId(), accountNo);
        //长链摘要
        String originalUrlDigest = CommonUtil.MD5(addRequest.getOriginalUrl());
        //生成短链码
        String shortLinkCode = shortLinkComponent.createShortLinkCode(addRequest.getOriginalUrl());
        //短链码重复标记
        boolean duplicateCodeFlag = false;
        //加锁
        //key1是短链码，ARGV[1]是accountNo,ARGV[2]是过期时间
        String script = "if redis.call('EXISTS',KEYS[1])==0 then redis.call('set',KEYS[1],ARGV[1]); redis.call('expire',KEYS[1],ARGV[2]); return 1;" +
                " elseif redis.call('get',KEYS[1]) == ARGV[1] then return 2;" +
                " else return 0; end;";
        Long result = redisTemplate.execute(new
                DefaultRedisScript<>(script, Long.class), Arrays.asList(shortLinkCode), accountNo, 100);
        if (result > 0){

            //先判断是否短链码被占用
            //C端处理
            if (EventMessageType.SHORT_LINK_ADD_LINK.name().equalsIgnoreCase(messageType)) {
                ShortLinkDO linkDO = shortLinkService.findByShortLinCode(shortLinkCode);
                if (linkDO == null){
                    ShortLinkDO build = ShortLinkDO.builder()
                        .accountNo(accountNo).code(shortLinkCode).title(addRequest.getTitle())
                        .originalUrl(addRequest.getOriginalUrl()).domain(domainDO.getValue())
                        .groupId(linkGroupDO.getId()).expired(addRequest.getExpired()).sign(originalUrlDigest)
                        .state(ShortLinkStateEnum.ACTIVE.name()).del(0)
                        .build();
                    shortLinkService.addShortLink(build);
                    return true;
                }else {
                    log.error("B端短链码重复:{}", eventMessage);
                    duplicateCodeFlag = true;
                }
            }else if (EventMessageType.SHORT_LINK_ADD_MAPPING.name().equalsIgnoreCase(messageType)){
                GroupCodeMappingDO byGroupIdAndMappingId = groupCodeMappingService.findByGroupIdAndMappingId(shortLinkCode, linkGroupDO.getId(), accountNo);
                    if (byGroupIdAndMappingId == null) {
                        //B端处理
                        GroupCodeMappingDO groupCodeMappingDO = GroupCodeMappingDO.builder()
                                .accountNo(accountNo).code(shortLinkCode).title(addRequest.getTitle())
                                .originalUrl(addRequest.getOriginalUrl()).domain(domainDO.getValue())
                                .groupId(linkGroupDO.getId()).expired(addRequest.getExpired())
                                .sign(originalUrlDigest).state(ShortLinkStateEnum.ACTIVE.name()).del(0)
                                .build();
                        groupCodeMappingService.add(groupCodeMappingDO);
                        return true;
                    }else {
                        log.error("B端短链码重复:{}", eventMessage);
                        duplicateCodeFlag = true;
                    }
            }

        } else {
            //加锁失败，自旋100毫秒，再调用； 失败的可能是短链码已经被占用，需要重新生成
            log.error("加锁失败:{}", eventMessage);

            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
            }
            duplicateCodeFlag = true;
        }

        if (duplicateCodeFlag){
            String newOriginalUrl = CommonUtil.addUrlPrefixVersion(addRequest.getOriginalUrl());
            addRequest.setOriginalUrl(newOriginalUrl);
            eventMessage.setContent(JsonUtil.obj2Json(addRequest));
            log.warn("短链码报错失败，重新生成:{}", eventMessage);
            handleAddShortLink(eventMessage);
        }
        return false;
    }

    @Override
    public Map<String, Object> pageByGroupId(ShortLinkPageRequest request) {
        Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();
        Map<String, Object> stringObjectMap = groupCodeMappingService.pageShortLinkByGroupId(request.getPage(), request.getSize(), accountNo, request.getGroupId());
        return stringObjectMap;
    }

    @Override
    public JsonData delShortLink(ShortLinkDelRequest request) {
        Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();
        EventMessage eventMessage = EventMessage.builder().accountNo(accountNo)
                .content(JsonUtil.obj2Json(request))
                .messageId(IDUtil.geneSnowFlakeID().toString())
                .eventMessageType(EventMessageType.SHORT_LINK_DEL.name())
                .build();
        rabbitTemplate.convertAndSend(rabbitMQDelConfig.getShortLinkEventExchange(), rabbitMQDelConfig.getShortLinkDelRoutingKey(),eventMessage);
        return JsonData.buildSuccess();
    }

    @Override
    public JsonData updateShortLink(ShortLinkUpdateRequest request) {
        Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();
        EventMessage eventMessage = EventMessage.builder().accountNo(accountNo)
                .content(JsonUtil.obj2Json(request))
                .messageId(IDUtil.geneSnowFlakeID().toString())
                .eventMessageType(EventMessageType.SHORT_LINK_UPDATE.name())
                .build();
        rabbitTemplate.convertAndSend(rabbitMQUpdateConfig.getShortLinkEventExchange(), rabbitMQUpdateConfig.getShortLinkUpdateRoutingKey(),eventMessage);
        return JsonData.buildSuccess();
    }

    @Override
    public boolean handleDelShortLink(EventMessage eventMessage) {
        Long accountNo = eventMessage.getAccountNo();
        String eventMessageType = eventMessage.getEventMessageType();
        ShortLinkDelRequest delRequest = JsonUtil.json2Obj(eventMessage.getContent(), ShortLinkDelRequest.class);
        if (EventMessageType.SHORT_LINK_DEL_LINK.name().equalsIgnoreCase(eventMessageType)){
            ShortLinkDO shortLinkDO = ShortLinkDO.builder().accountNo(accountNo).code(delRequest.getCode()).build();
            int rows = shortLinkService.del(shortLinkDO);
            log.debug("更新C端短链，rows={}",rows);
            return true;
        } else if(EventMessageType.SHORT_LINK_DEL_MAPPING.name().equalsIgnoreCase(eventMessageType)){
            GroupCodeMappingDO groupCodeMappingDO = GroupCodeMappingDO.builder()
                    .id(delRequest.getMappingId()).accountNo(accountNo)
                    .groupId(delRequest.getGroupId()).build();
            groupCodeMappingService.del(groupCodeMappingDO);
            return true;
        }
        return false;
    }

    @Override
    public boolean handleUpdateShortLink(EventMessage eventMessage) {
        Long accountNo = eventMessage.getAccountNo();
        String eventMessageType = eventMessage.getEventMessageType();
        ShortLinkUpdateRequest updateRequest = JsonUtil.json2Obj(eventMessage.getContent(), ShortLinkUpdateRequest.class);
        //短链域名校验
        DomainDO domainDO = checkDomain(updateRequest.getDomainType(),updateRequest.getDomainId(),accountNo);
        if (EventMessageType.SHORT_LINK_UPDATE_LINK.name().equalsIgnoreCase(eventMessageType)){
            ShortLinkDO shortLinkDO = ShortLinkDO.builder().accountNo(accountNo).code(updateRequest.getCode())
                    .title(updateRequest.getTitle()).domain(domainDO.getValue()).build();
            int rows = shortLinkService.update(shortLinkDO);
            log.debug("更新C端短链，rows={}",rows);
            return true;
        } else if(EventMessageType.SHORT_LINK_UPDATE_MAPPING.name().equalsIgnoreCase(eventMessageType)){
            GroupCodeMappingDO groupCodeMappingDO = GroupCodeMappingDO.builder()
                    .id(updateRequest.getMappingId()).accountNo(accountNo)
                    .groupId(updateRequest.getGroupId()).title(updateRequest.getTitle())
                    .domain(domainDO.getValue()).build();
            int rows = groupCodeMappingService.update(groupCodeMappingDO);
            log.debug("更新C端短链，rows={}",rows);
            return true;
        }
        return false;
    }

    @Override
    public Integer update(ShortLinkDO shortLinkDO) {
        int rows = shortLinkMapper.update(null, new UpdateWrapper<ShortLinkDO>()
                .eq("code", shortLinkDO.getCode())
                .eq("del", 0)
                .eq("account_no",shortLinkDO.getAccountNo())
                .set("title", shortLinkDO.getTitle())
                .set("domain", shortLinkDO.getDomain()));
        return rows;
    }

    private LinkGroupDO checkLinkGroup(Long groupId, Long accountNo) {
        LinkGroupDO linkGroupDO = linkGroupService.detailByIdAndNo(groupId, accountNo);
        Assert.notNull(linkGroupDO, "组名不合法");
        return linkGroupDO;
    }

    /**
     * 校验域名
     *
     * @param domainType
     * @param domainId
     * @param accountNo
     * @return
     */
    private DomainDO checkDomain(String domainType, Long domainId, Long accountNo) {
        DomainDO domainDO ;
        if (DomainTypeEnum.CUSTOM.name().equalsIgnoreCase(domainType)){
            domainDO = domainService.findById(domainId, accountNo);
        }else {
            domainDO = domainService.findByDomainTypeAndID(domainId, DomainTypeEnum.OFFICIAL);
        }
        Assert.notNull(domainDO, "短链域名不合法");
        return domainDO;
    }
}
